#!/usr/bin/perl -I /opt/hp/nv/lib/perl
#
# (C) Copyright 2010- Hewlett-Packard Development Company, L.P.
#
#  $Id: dbperms.pl 18812 2012-06-19 07:17:25Z yi.wang4@hp.com $
#

use strict;
use 5.008000;
use warnings;
use File::Basename;
use English;
use Getopt::Long;
use POSIX;
use Fcntl ':mode';

# perl module containing log handling routines
use lib '/opt/hp/nv/lib/perl';
use config ':all';
use bright ':all';

# possible error returns
my $OK=0;
my $WARN=1;
my $CRITICAL=2;
my $UNKNOWN=3;

my $proceed=1;
my $done=0;

# values particular to this script
my $myscriptname = "dbperms";
my $revision = '$Revision: 18812 $';

# Status file for script
my $status_file="$nvpath/instance/status/" . $myscriptname . '.stat';

# Command line options
my $squser = "squser1";
my $group = "seaquest";
my $config;
my $dryrun = 0;

use constant {
    PERM_SLICED => 0750, # for sliced, the mountpint is owned by the squser
    PERM_SHARED => 0770, # for shared, the mountpoint is owned by root, 
};

my $permission = PERM_SLICED;

# Define path of system commands
my $cmd_multipath = '/sbin/multipath';
my $cmd_egrep = '/bin/egrep';
my $cmd_grep = '/bin/grep';
my $cmd_wc = '/usr/bin/wc';
my $cmd_mount = '/bin/mount';
my $cmd_umount = '/bin/umount';

my @gv_mpath_array = ();

#
# This sub calls module routine to log informational message to log file
# after first prepending program name; timestamp will be added in
# Perl module
#
sub log_msg
{
my($message) =  @_;
my $infomessage;

    $infomessage = basename($0) . ": INFO: ". $message . "\n";
    $dryrun ? print $infomessage : instance_log_event($infomessage);

}

#
# This sub calls module routine to log warning message to log file
# after first prepending program name and Warning label; a timestamp
# will be added in Perl module
#
sub log_warn
{
my($message) =  @_;
my $warnmessage;

    $warnmessage = basename($0) . ": WARNING: ". $message . "\n";
    $dryrun ? print $warnmessage : instance_log_event($warnmessage);

}

#
# This sub calls module routine to log error message to log file
# after first prepending program name; timestamp will be added in
# Perl module; the program will then exit
#
sub logerr_n_die
{
my($message) =  @_;
my $errormessage;

    $errormessage = basename($0) . ": ERROR: ". $message . "\n";
    instance_log_event($errormessage) unless ($dryrun);

    print STDERR $errormessage;
    exit($CRITICAL);

}

sub run_cmd($)
{
    my $cmd = shift;
    if ($dryrun) {
        print STDOUT $cmd . "\n";
        $! = 0;
    }
    else {
        system($cmd);
    }
}


#
# This sub checks for any requirements that must be met before
# the configuration is performed.
#
sub pre_req_check
{
    my $cnt = 0;
    my ($name, $psw);

    # Must be the root user to execute this script.
    if (!$dryrun && $EUID != 0)    {
        logerr_n_die("Must be the root user to execute this script.");
    }

    # This script must not be executed on a head node.
    if (cm_Check()) {
        logerr_n_die("This script must not be run on a head node")
    }

    # First, make sure that we can see mpath devices
    run_cmd("$cmd_multipath -v0");

    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0)) {
        logerr_n_die("Unable to rescan the multipath devices");
    }

    # Get list of the multipath devices
    chomp(my @mpath_output = `$cmd_multipath -ll`);
    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0)) {
        logerr_n_die("Unable to get list of multpath devices");
    }

    my ($dev_line, $lun_line);
    # Format of mpath_output: 
    # mpath600 (3600c0ff000138bea00ce744f01000000) dm-35 HP,P2000 G3 FC
    # [size=112G][features=0][hwhandler=0][rw]
    # \_ round-robin 0 [prio=60][active]
    #  \_ 1:0:1:61 sdbp 68:48   [active][ready]
    #  \_ 0:0:1:61 sdcu 70:32   [active][ready]
    while( ($dev_line,undef,undef,undef,$lun_line) = splice(@mpath_output,0,5)){

        if($dev_line =~ /(mpath\d+)/){

            my $dev = $1;

            # Exclude shared storage/Bright (lun 60 and over)
            if ($lun_line =~ /[01]:\d+:\d+:[6-9]\d+/) {
                log_msg("Skip shared storage $dev");
                next;
            }

            push @gv_mpath_array, $dev;
        }
    }

    if (scalar(@gv_mpath_array) <= 0) {
        logerr_n_die("There do not appear to be any multipath devices");
    }

    # Ensure that the file systems are not already mounted.
    chomp ($cnt = `$cmd_mount | $cmd_grep "mpath" | $cmd_wc -l`);
    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0)) {
        logerr_n_die("Failed to check file system mount status");
    }

    if ( $cnt > 0) {
        logerr_n_die("File systems already mounted")
    }

    return($OK);
}

#
# This sub verifies that the work done is complete and in such a state
# that it is ready for the next step to be done
#
sub post_check
{
    return($OK) if ($dryrun);

    my $sq_userid;
    if($config eq 'shared'){
        $sq_userid = 0; # root
        $permission = PERM_SHARED;
    }
    else {   
        # Convert the user and group names to ids.
        $sq_userid = getpwnam $squser || logerr_n_die("Invalid user name:$squser");
    }
    my $groupid = getgrnam $group || logerr_n_die("Invalid group name:$group");

    my ($mkdir_ok, $mount_ok);
    my $errs = 0;

    my $mnt_dir;
    for my $mpath (@gv_mpath_array) {
        log_msg("Checking $mpath");
        ($mkdir_ok, $mount_ok) = 0;

        my $fs = "/dev/mapper/${mpath}p1";
        next if (! -e $fs);

        $mnt_dir = "/mnt/$mpath";

        run_cmd("/bin/mkdir $mnt_dir");
        log_warn("Mkdir $mnt_dir") if (WIFEXITED($?) && (WEXITSTATUS($?) != 0));

        $mkdir_ok = 1;

        run_cmd("$cmd_mount $fs $mnt_dir");
        log_warn("Mount $mnt_dir") if (WIFEXITED($?) && (WEXITSTATUS($?) != 0));

        $mount_ok = 1;

        my ($mode, $uid, $gid) = (stat($mnt_dir))[2, 4, 5];
        my $filemode = $mode & (S_IRWXU | S_IRWXG | S_IRWXO);
        
        if ($filemode != $permission) {
            log_msg("Permission of $mnt_dir is incorrect, $mode : $permission");
            $errs++;
            next;
        }
        if ($uid != $sq_userid || $gid != $groupid) {
            log_msg("Owner of $mnt_dir is incorrect");
            $errs++;
            next;
        }
    }
    continue {
        if ($mount_ok) {
            run_cmd("$cmd_umount $mnt_dir");
            logerr_n_die("Umount $mnt_dir") 
                if (WIFEXITED($?) && (WEXITSTATUS($?) != 0));
        }
        
        run_cmd("/bin/rmdir $mnt_dir") if ($mkdir_ok);
    }

    return($OK) if ($errs == 0);

    logerr_n_die("Post check failed, due to $errs warnings");
}

# CHANGE FOR YOUR NEEDS
#
# This sub checks the script status to determine whether
# the script has already been run successfully.
# The status file could just hold "completed", for example,
# or it could hold a count of how many things were processed last time,
# e.g. number of nodes, or number of disks, etc.  So that a comparison
# can be made to see if processing must be done again.
#
sub get_status
{
my $prefix = shift;
    # Keep the open and close of the file along with the line processing

    # if we haven't yet written this file then we surely must proceed
    return($proceed) if (!-e $status_file);
    
    open(IFH,$status_file) || logerr_n_die("Can't open file $status_file: $!\n");
    while (<IFH>) # loop through the whole file
    {
        next if (/^\s*$/);
        next if (/^\#/);
    
        # ADD HERE THE PROCESSING THAT MAKES SENSE FOR YOUR CODE!
        # IN THIS EXAMPLE WE CHECK FOR 'completed'
        if (/^$prefix:completed/) {
            close(IFH);
            return($done);
        }
    }
    
    close(IFH);
    return($proceed);
}

# CHANGE FOR YOUR NEEDS
#
# This sub sets the script status to be examined
# the next time the script is run.
# The status file could just hold "completed", for example,
# or it could hold a count of how many things were processed last time,
# e.g. number of nodes, or number of disks, etc.  So that a comparison
# can be made to see if processing must be done again.
#
sub set_status
{
    my ($statusmsg, $prefix) = @_;

    # Keep the open and close of the file
    open(OFH,">>$status_file") || logerr_n_die("Can't open file $status_file: $!\n");

    # ADD HERE THE PROCESSING THAT MAKES SENSE FOR YOUR CODE!
    print(OFH "$prefix:$statusmsg\n");

    close(OFH);

}

#
# This sub creates a temporary mount point for each of the multipath file
# systems, mounts the file system, sets the required ownership and
# permission for the directory and then unmounts the file systsem before
# removing the temporary mount point.
#
sub perform_config
{
    my $cnt;
    my $dir;
    my $dir_flag = 0;
    my $errs = 0;
    my $fs;
    my $mnt_flag = 0;

    # Keep the following checks to determine whether the
    # script is being forced to be re-run, and to check
    # whether this script completed successfully
    my $force_flag = get_force_flag();
    my $status_value = get_status($squser);

    return($OK) if (!$force_flag && ($status_value == $done));

    my $user_id;
    if($config eq 'shared'){
        $user_id = 0;
        $permission = PERM_SHARED;
    }
    else {   
        # Convert the user and group names to ids.
        $user_id = getpwnam $squser || logerr_n_die("Invalid user name:$squser");
    }
    my $group_id = getgrnam $group || logerr_n_die("Invalid group name:$group");

    # Mount each directory in turn and set the required permission
    for my $dev (@gv_mpath_array) {

        log_msg("Processing $dev");

        # Create a temporary mount point for this file system.
        $dir = "/mnt/$dev";
        run_cmd("/bin/mkdir $dir");

        if (WIFEXITED($?) && (WEXITSTATUS($?) != 0)) {
            log_warn("Unable to create $dir directory");
            $errs++;
            next;
        } 
        else {
            $dir_flag = 1;
        }

        # Mount the file system on the new mount point.
        $fs = "/dev/mapper/${dev}p1";
        next if (! -e $fs);

        run_cmd("$cmd_mount $fs $dir");
        if (WIFEXITED($?) && (WEXITSTATUS($?) != 0)) {
            log_warn("Unable to mount $fs on $dir");
            $errs++;
            next;
        } 
        else {
            system "/bin/rm -rf $dir/lost+found";
            $mnt_flag = 1;
        }
        
        my $own_dir = $dir;
        
        print "chown $user_id, $group_id, $own_dir\n" if ($dryrun);
        $cnt = chown $user_id, $group_id, $own_dir;
        if ($cnt != 1) {
            log_warn("Unable to chown $own_dir");
            $errs++;
            next;
        }

        print "chmod $permission, $own_dir\n" if ($dryrun);
        $cnt = chmod $permission, $own_dir;
        if ($cnt != 1) {
            log_warn("Unable to chmod $own_dir");
            $errs++;
            next;
        }
    } continue {
        # We tidy up any temporary mount points etc. here rather than in
        # the main loop to avoid duplication of code.

        # Unmount the file system.
        if ($mnt_flag) {
            run_cmd("$cmd_umount $dir");
            if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
            {
                log_warn("Unable to unmount $fs");
                $errs++;
            }
        }

        # Remove the temporary mount point.
        if ($dir_flag) {
            run_cmd("/bin/rmdir $dir");
            if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
            {
                log_warn("Unable to rmdir $dir");
                $errs++;
            }
        }

        # Reset or flags ready for the next itteration.
        $dir_flag = 0;
        $mnt_flag = 0;
    }

    if ($errs == 0) {
        set_status("completed", $squser);
        return($OK);
    } else {
        set_status("failed", $squser);
        logerr_n_die("Unable to set ownership and permission", $squser);
    }

}


#
# usage
#
sub usage
{
print <<"EOF";

This script sets the permission to the user and group that will be using the storage.  
This script must be able to set permission on a mimimum of a half rack in every rack.  

    $0 [--help] [--squser=] [--group=] [--config=sliced|shared] [--test]

EOF

exit($UNKNOWN);
}

#
# This sub handles arguments from user, and sets necessary default values
#
sub handle_options
{
    &GetOptions( "help",  \&usage,
                 "squser=s", \$squser,
                 "group=s", \$group,
                 "config=s", \$config,
                 "test|t",  \$dryrun,
                 ) or usage();
    
    $config = lc($config);
    unless($config=~/^(sliced|shared)$/){
        logerr_n_die("Config type must be sliced or shared");
    }
}

### Main ###

# The only option supported right now is help
# generate usage output for anything else.
log_msg("Starting script: revision number $revision");

handle_options;

pre_req_check;

perform_config;

post_check;

log_msg("Script completed successfully!");

# Return success
exit($OK);


